package com.sunday.common.mq.rocket.brave.study.base.e7_Retry;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.junit.jupiter.api.Test;

import java.util.concurrent.TimeUnit;

/**
 * 1.2 消息发送失败处理方式
 * Producer的send方法本身支持内部重试，重试逻辑如下：
 * <p>
 * 至多重试2次。
 * 如果同步模式发送失败，则轮转到下一个Broker，如果异步模式发送失败，则只会在当前Broker进行重试。
 * 这个方法的总耗时时间不超过sendMsgTimeout设置的值，默认10s。
 * 如果本身向broker发送消息产生超时异常，就不会再重试。
 * 以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高，
 * 建议应用增加相应的重试逻辑：比如调用send同步方法发送失败时，则尝试将消息存储到db，然后由后台线程定时重试，确保消息一定到达Broker。
 * <p>
 * 上述db重试方式为什么没有集成到MQ客户端内部做，而是要求应用自己去完成，主要基于以下几点考虑：
 * 首先，MQ的客户端设计为无状态模式，方便任意的水平扩展，且对机器资源的消耗仅仅是cpu、内存、网络。其次，如果MQ客户端内部集成一个KV存储模块，
 * 那么数据只有同步落盘才能较可靠，而同步落盘本身性能开销较大，所以通常会采用异步落盘，又由于应用关闭过程不受MQ运维人员控制，
 * 可能经常会发生 kill -9 这样暴力方式关闭，造成数据没有及时落盘而丢失。第三，Producer所在机器的可靠性较低，一般为虚拟机，不适合存储重要数据。
 * <p>
 * 综上，建议重试过程交由应用来控制。
 * <p>
 * 消息发送成功或者失败要打印消息日志，务必要打印SendResult和key字段。send消息方法只要不抛异常，
 * 就代表发送成功。发送成功会有多个状态，在sendResult里定义。以下对每个状态进行说明：
 * <p>
 * SEND_OK
 * 消息发送成功。要注意的是消息发送成功也不意味着它是可靠的。要确保不会丢失任何消息，
 * 还应启用同步Master服务器或同步刷盘，即SYNC_MASTER或SYNC_FLUSH。
 * <p>
 * FLUSH_DISK_TIMEOUT
 * 消息发送成功但是服务器刷盘超时。此时消息已经进入服务器队列（内存），只有服务器宕机，消息才会丢失。
 * 消息存储配置参数中可以设置刷盘方式和同步刷盘时间长度，如果Broker服务器设置了刷盘方式为同步刷盘，
 * 即FlushDiskType=SYNC_FLUSH（默认为异步刷盘方式），当Broker服务器未在同步刷盘时间内（默认为5s）完成刷盘，
 * 则将返回该状态——刷盘超时。
 * <p>
 * FLUSH_SLAVE_TIMEOUT
 * 消息发送成功，但是服务器同步到Slave时超时。此时消息已经进入服务器队列，只有服务器宕机，消息才会丢失。
 * 如果Broker服务器的角色是同步Master，即SYNC_MASTER（默认是异步Master即ASYNC_MASTER），
 * 并且从Broker服务器未在同步刷盘时间（默认为5秒）内完成与主服务器的同步，则将返回该状态——数据同步到Slave服务器超时。
 * <p>
 * SLAVE_NOT_AVAILABLE
 * 消息发送成功，但是此时Slave不可用。如果Broker服务器的角色是同步Master，
 * 即SYNC_MASTER（默认是异步Master服务器即ASYNC_MASTER），
 * 但没有配置slave Broker服务器，则将返回该状态——无Slave服务器可用。
 */
@Slf4j
public class RetryProducer {

    @Test
    public void producer() throws Exception {

        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("retry_producer_group_name");
        // 设置NameServer的地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        // 失败的情况重发3次
        producer.setRetryTimesWhenSendFailed(3);
        // 启动Producer实例
        producer.start();

        for (int i = 0; i < 1; i++) {
            // 创建消息，并指定Topic，Tag和消息体
            Message msg = new Message(
                    "TopicTest" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ ").getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );

            // 消息在1S内没有发送成功，就会重试
            SendResult sendResult = producer.send(msg, 1000);
            // 通过sendResult返回消息是否成功送达
            log.info("{}", sendResult);
            TimeUnit.SECONDS.sleep(5);
        }

        // 如果不再发送消息，关闭Producer实例。
        producer.shutdown();
    }


}
